﻿using System;
using DimOS.Constants;

namespace DimOS.H
{

    public class Function
    {

        protected string Name;

        protected string Operation;

        protected ValueType Type { get; set; }
        
        private Function Implementation;

        private static readonly Literal Literal = new Literal();
        private static readonly Empty Empty = new Empty();

        public Function()
        {
            Implementation = this;
            Type = ValueType.None;
        }

        internal Function(Script source, string name, char character,
            ref string operation, ValueType type = ValueType.None)
        {
            Type = type; if (name.Length == 0 && (character == Tokens
            .ParameterLeft || !source.Valid)) { Implementation = Empty; return; }
         
            if (string.IsNullOrWhiteSpace(operation)) Implementation = null; else
            { var entry = FunctionEntry.Find(Functions.Operations, operation);
            Implementation = entry != null ? entry.Function.New() : null; }

            if (Implementation != null) { Implementation.Name = name; Implementation
            .Operation = operation; operation = null; return; } var start = name
            .IndexOf(Tokens.ValueLeft); if (start >= 0) { start -= name.Length - 1;
            var end = 0; var specifiers = Value.ElementSpecifiers(ref name, ref end);
            if (specifiers.Count != 0) { Implementation = Find(name) as Variable; if
            (Implementation != null) { source.Traverse(start); if (operation != null)
            source.Traverse(-operation.Length); end -= name.Length; (Implementation as
            Variable).Specifiers = specifiers; (Implementation as Variable).Level = end;
            return; } } } if (Implementation != null) return;
            
            Implementation = Find(name); if (Implementation != null) return;
            if(Implementation == Literal && string.IsNullOrWhiteSpace(name))
            { throw new InvalidOperationException("Error: Failed to process"
            + " chunk (" + (!string.IsNullOrWhiteSpace(operation) ? operation
            : character.ToString()) +") in [" + character + source.Substring
            (source.Pointer, Engine.OutputLimit) + "]."); } Literal.Item =
            name; Implementation = Literal;
        }

        public static Function Find(string item)
        { FunctionEntry entry; if (Functions.Stack.Count > 0)
          { entry = FunctionEntry.Find(Functions.Stack[Functions
          .Stack.Count - 1].Functions, item); if (entry != null)
          return entry.Function; } entry = FunctionEntry.Find
          (Functions.List, item); if (entry != null) return
          entry.Function.New(); return null; }

        public static bool Exists(string name)
        { var result = false; if (Functions.Stack.Count > 0)
          { var entry = FunctionEntry.Find(Functions.Stack
          [Functions.Stack.Count - 1].Functions, name); result
          = entry != null; } return result || FunctionEntry.Find
          (Functions.List, name) != null; }

        public static void Add(string name, Function function)
        { var entry = FunctionEntry.Find(Functions.List, name);
          if (entry != null) entry.Function = function; else
          Functions.List.Add(new FunctionEntry(name, function));
          if (string.IsNullOrWhiteSpace(function.Name)) function
          .Name = name; }

        public static void AddOperation(string name, Function function)
        { var entry = FunctionEntry.Find(Functions.Operations, name);
          if (entry != null) entry.Function = function; else Functions
          .Operations.Add(new FunctionEntry(name, function)); }

        public static void AddVariable(string name, Function function)
        { function.Name = name; if (Functions.Stack.Count > 0) { var
          stackEntry = Functions.Stack[Functions.Stack.Count - 1]; var
          functionEntry = FunctionEntry.Find(stackEntry.Functions, function
          .Name); if (functionEntry != null) functionEntry.Function =
          function; else stackEntry.Functions.Add(new FunctionEntry
          (function.Name, function)); } else Add(name, function); }

        public Value Call(Script source, bool silent = false)
        {
            switch (Implementation.Type)
            {
                case ValueType.Function: return ((Definition)
                Implementation).Evaluate(source, silent);

                case ValueType.Argument: return ((Variable)
                Implementation).Evaluate(source, silent);
            }
            return Implementation.Evaluate(source, silent);
        }

        protected virtual Value Evaluate(Script source, bool silent = false)
        { return new Value(); /*Must be overriden in derived classes*/ }

        protected virtual Function New()
        { return this; /*Can be overriden in derived classes*/ }
    }
}
